Skip to content

FIx method resolution in number slots when a single method is a subclass#6228

Open
MatthieuDartiailh wants to merge 7 commits into
PyO3:mainfrom
MatthieuDartiailh:binary-op-fix
Open

FIx method resolution in number slots when a single method is a subclass#6228
MatthieuDartiailh wants to merge 7 commits into
PyO3:mainfrom
MatthieuDartiailh:binary-op-fix

Conversation

@MatthieuDartiailh

Copy link
Copy Markdown
Contributor

Closes #6024

When a class implement both half of a slot (__add__ and __radd__) we use the old dispatch strategy except that we dispatch to the appropriate according to the type of the arguments. In other cases, we use a super object to support multiple inheritance with pure Python classes.

Comment thread newsfragments/6228.fixed.md Outdated
Comment thread tests/test_arithmetics.rs
Comment on lines +524 to +532
// ============================================================================
// Binary Operator Inheritance Tests
// Tests super() delegation with parent classes and multiple inheritance
// ============================================================================

/// Test 1: Parent Forward → Child Reflected (Rust only)
/// Parent implements __add__, child implements __radd__
#[pyclass(subclass)]
struct AddBase;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's best to delete these AI comments as well (looks verbose)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those were AI generated but at my demand to make it easier to navigate the tests (there are a lot and it is not always straightforward to figure out what they test). But I am fine removing them.

Comment thread src/impl_/pyclass.rs
Comment thread src/impl_/pyclass.rs

let lhs_obj = unsafe { $crate::Bound::from_borrowed_ptr(py, _slf) };
if lhs_obj.is_instance_of::<$cls>() {
return unsafe { collector.__pow__(py, _slf, _other, _mod) };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, we can add a fast path so that direct type object comparisons avoid the creation of a Bound:

let lhs_type = unsafe { (*_slf).ob_type };
let type_ = <$cls as $crate::PyTypeInfo>::type_object_raw(py);
if lhs_type == type_ {
	return unsafe { collector.$lhs(py, _slf, _other) };
}

Comment thread src/impl_/pyclass.rs
}};

// Only forward available
($cls:ty, true, false) => {{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same type optimization as above:

                     let collector = PyClassImplCollector::<$cls>::new();
                       
-                    let lhs_obj = unsafe { $crate::Bound::from_borrowed_ptr(py, _slf) };
-                    if lhs_obj.is_instance_of::<$cls>() {
+                    let lhs_type = unsafe { (*_slf).ob_type };
+                    let type_ = <$cls as $crate::PyTypeInfo>::type_object_raw(py);
+                    if lhs_type == type_ || unsafe {
+                        $crate::Bound::from_borrowed_ptr(py, _slf).is_instance_of::<$cls>()
+                    } {
                         return unsafe { collector.$lhs(py, _slf, _other) };
                     }

-                    let rhs_obj = unsafe { $crate::Bound::from_borrowed_ptr(py, _other) };
-                    if rhs_obj.is_instance_of::<$cls>() {
+                    let rhs_type = unsafe { (*_other).ob_type };
+                    if rhs_type == type_ || unsafe {
+                        $crate::Bound::from_borrowed_ptr(py, _other).is_instance_of::<$cls>()
+                    } {
                         return unsafe { collector.$rhs(py, _other, _slf) };
                     }

@MatthieuDartiailh
MatthieuDartiailh marked this pull request as ready for review July 24, 2026 22:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

reversible arithmetic operators don't respect subclassing if only one arm implemented

2 participants